2

JavaScript 闭包是怎么通过作用域链霸占更多内存的?

本文是作者学习《JavaScript 高级程序设计》7.2第一小节的一点个人理解,详细教程请参考原教材。

在弄清楚这个问题之前,我们有必要搞清楚下面这几个问题:

首先,什么是闭包?

定义:闭包 是指 有权访问另一个函数作用域中变量的函数。

怎么创建闭包?

在一个函数内部创建另一个函数,是创建闭包最常见的方式。

一个一般的函数执行的时候都发生了什么?

  • 创建一个执行环境,里面有这个函数的变量对象,保存了函数可访问的所有数据。

  • 创建了作用域链,用来保证可访问变量的有序访问。

  • 函数执行过程创建了一个函数的活动对象,作用域链的最前端指向这个对象。

  • 函数读取写入值,在作用域链里面查找。

  • 函数执行完毕返回值后:执行环境、作用域链和活动对象一并销毁。

理解了这些之后,考察下原书的代码:

function createCompare(property){
    return function(obj1,obj2){
        var value1=obj1.property;
        var value2=obj2.property;
        
        if (value2 < value1){
            return 1;
        } else if (value2 >value1){
            return 1;
        } else {
            return 0;
        }
    };
}

var compareNames = createCompare("name");
var result = compareNames({name : "mars1"},{name : "mars2"});

其中,createCompare 函数返回了一个匿名函数,这个匿名函数就是一个闭包。它引用了createCompare函数的参数变量property。

创建compareNames 变量的时候,就创建了createCompare函数的活动变量:其中property = name etc. 同时返回了匿名函数给compareNames变量,它也有自己的执行环境与作用域链,作用域链引用了创建的compareNames活动对象。

result 定义过程,创建了compareNames的活动对象,其中obj1 = {name : "mars1"} etc.

这样,为result 赋值的compareNames函数执行环境的作用域链如下:

compareNames活动对象 → createCompare活动对象 → 全局对象。

那么问题来了,这个闭包是怎么导致占用更多内存的?


函数执行完毕,compareNames 变量并未消失,不会被垃圾回收,而它指向的闭包(匿名函数)的作用域链引用着createCompare 函数的活动变量。所以createCompare函数虽然执行完毕返回了匿名函数,但是它的活动对象并不会和执行环境、作用域链一样被销毁,因为还有compareNames的作用域链在引用。这样就相当于闭包携带了包含它的函数活动对象。

因此要释放compareNames携带的作用域,必须手动解除引用:

var compareNames = null;

Mars
4 声望0 粉丝

姚一鸣 (Mars)。